/*
 * Copyright (C) 2019-2020 Yaong <yaongtime@gmail.com>
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdio.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <errno.h>

#include "u_math.h"

#include "vc4_private.h"
#include "vc4_memory.h"

#include <fcntl.h>

#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm-uapi/drm.h>
#include "drm-uapi/vc4_drm.h"

VkResult
vc4_vk_bo_init_alloc(struct vc4_device *dev, struct vc4_bo *bo, uint32_t size)
{
    struct drm_vc4_create_bo create;
    int ret;

    size = align(size, 4096);

    memset(&create, 0, sizeof(create));
    create.size = size;

    ret = drmIoctl(dev->fd, DRM_IOCTL_VC4_CREATE_BO, &create);
    bo->handle = create.handle;
    bo->size = size;

    if (ret != 0) {
        return vk_errorf(dev->instance, VK_ERROR_OUT_OF_DEVICE_MEMORY,
            "Couldn't alloc BO: %s, size %u\n", strerror(errno), size);
	}

    return VK_SUCCESS;
}

struct vc4_bo *
vc4_vk_bo_alloc(struct vc4_device *dev, uint32_t size)
{
    struct vc4_bo *bo = vk_zalloc(&dev->vk.alloc, sizeof(struct vc4_bo), 8,
                    VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
    if (!bo)
        return NULL;

    if(vc4_vk_bo_init_alloc(dev, bo, size) != VK_SUCCESS) {
        vk_free(&dev->vk.alloc, bo);
        return NULL;
    }

    return bo;
}

void vc4_vk_bo_free_mem(struct vc4_device *dev, struct vc4_bo *bo)
{
    struct drm_gem_close c;

    assert(bo->handle);

    if (bo->map)
        munmap(bo->map, bo->size);

    memset(&c, 0, sizeof(c));
    c.handle = bo->handle;

    int ret = drmIoctl(dev->fd, DRM_IOCTL_GEM_CLOSE, &c);
    if (ret != 0) {
        vk_errorf(dev->instance, VK_ERROR_OUT_OF_DEVICE_MEMORY,
                  "close object %d: %s\n", bo->handle, strerror(errno));
    }
}

void vc4_vk_bo_free(struct vc4_device *dev, struct vc4_bo *bo)
{
    vc4_vk_bo_free_mem(dev, bo);
    vk_free(&dev->vk.alloc, bo);
}

int
vc4_gem_export_dmabuf(const struct vc4_device *dev, uint32_t handle)
{
   int prime_fd;
   int ret = drmPrimeHandleToFD(dev->physical_device->local_fd, handle,
                                DRM_CLOEXEC, &prime_fd);

   return ret == 0 ? prime_fd : -1;
}

static int vc4_wait_bo_ioctl(int fd, uint32_t handle, uint64_t timeout_ns)
{
        struct drm_vc4_wait_bo wait = {
                .handle = handle,
                .timeout_ns = timeout_ns,
        };
        int ret = drmIoctl(fd, DRM_IOCTL_VC4_WAIT_BO, &wait);
        if (ret == -1)
                return -errno;
        else
                return 0;

}

static bool
vc4_bo_wait(struct vc4_bo *bo, uint64_t timeout_ns, const char *reason, int fd)
{
        // if (unlikely(vc4_debug & VC4_DEBUG_PERF) && timeout_ns && reason) {
        //         if (vc4_wait_bo_ioctl(screen->fd, bo->handle, 0) == -ETIME) {
        //                 fprintf(stderr, "Blocking on %s BO for %s\n",
        //                         bo->name, reason);
        //         }
        // }

        int ret = vc4_wait_bo_ioctl(fd, bo->handle, timeout_ns);
        if (ret) {
                if (ret != -ETIME) {
                        fprintf(stderr, "wait failed: %d\n", ret);
                        abort();
                }

                return false;
        }

        return true;
}

static void *
vc4_bo_map_unsynchronized(struct vc4_bo *bo, int fd)
{
        uint64_t offset;
        int ret;

        if (bo->map)
                return bo->map;

        struct drm_vc4_mmap_bo map;
        memset(&map, 0, sizeof(map));
        map.handle = bo->handle;
        ret = drmIoctl(fd, DRM_IOCTL_VC4_MMAP_BO, &map);
        offset = map.offset;
        if (ret != 0) {
                fprintf(stderr, "map ioctl failure\n");
                abort();
        }

        bo->map = mmap(NULL, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
        if (bo->map == MAP_FAILED) {
                fprintf(stderr, "mmap of bo %d (offset 0x%016llx, size %d) failed\n",
                        bo->handle, (long long)offset, bo->size);
                abort();
        }
        // VG(VALGRIND_MALLOCLIKE_BLOCK(bo->map, bo->size, 0, false));

        return bo->map;
}

VkResult
vc4_bo_map(struct vc4_device *device, struct vc4_bo *bo)
{
        vc4_bo_map_unsynchronized(bo, device->fd);

        bool ok = vc4_bo_wait(bo, PIPE_TIMEOUT_INFINITE, "bo map", device->fd);
        if (!ok) {
                fprintf(stderr, "BO wait for map failed\n");
                abort();
        }

        return TRUE;
}

void
vc4_bo_unmap(struct vc4_device *device, struct vc4_bo *bo)
{
   assert(bo && bo->map);

   munmap(bo->map, bo->size);
//    VG(VALGRIND_FREELIKE_BLOCK(bo->map, 0));
   bo->map = NULL;
}

static void *
vc4_shader_bo_map_unsynchronized(struct vc4_bo *bo, int fd)
{
        uint64_t offset;
        int ret;

        if (bo->map)
                return bo->map;

        struct drm_vc4_mmap_bo map;
        memset(&map, 0, sizeof(map));
        map.handle = bo->handle;
        ret = drmIoctl(fd, DRM_IOCTL_VC4_MMAP_BO, &map);
        offset = map.offset;
        if (ret != 0) {
                fprintf(stderr, "map ioctl failure\n");
                abort();
        }

        bo->map = mmap(NULL, bo->size, PROT_READ, MAP_SHARED, fd, offset);
        if (bo->map == MAP_FAILED) {
                fprintf(stderr, "mmap of bo %d (offset 0x%016llx, size %d) failed\n",
                        bo->handle, (long long)offset, bo->size);
                abort();
        }
        // VG(VALGRIND_MALLOCLIKE_BLOCK(bo->map, bo->size, 0, false));

        return bo->map;
}

VkResult
vc4_shader_bo_map(struct vc4_device *device, struct vc4_bo *bo)
{
        vc4_shader_bo_map_unsynchronized(bo, device->fd);

        bool ok = vc4_bo_wait(bo, PIPE_TIMEOUT_INFINITE, "bo map", device->fd);
        if (!ok) {
                fprintf(stderr, "BO wait for map failed\n");
                abort();
        }

        return TRUE;
}

struct vc4_bo *
vc4_bo_alloc_shader(struct vc4_device *device, const void *data, uint32_t size)
{
        struct vc4_bo *bo;
        int ret;

        bo = vk_zalloc(&device->vk.alloc, sizeof(struct vc4_bo), 8,
                      VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
        if (!bo)
                return NULL;

        // pipe_reference_init(&bo->reference, 1);
        // bo->screen = screen;
        bo->size = align(size, 4096);
        // bo->name = "code";
        bo->private = false; /* Make sure it doesn't go back to the cache. */

        struct drm_vc4_create_shader_bo create = {
                .size = size,
                .data = (uintptr_t)data,
        };

        ret = drmIoctl(device->fd, DRM_IOCTL_VC4_CREATE_SHADER_BO,
                        &create);
        bo->handle = create.handle;

        if (ret != 0) {
                fprintf(stderr, "create shader ioctl failure\n");
                abort();
        }

        // screen->bo_count++;
        // screen->bo_size += bo->size;
        // if (dump_stats) {
        //         fprintf(stderr, "Allocated shader %dkb:\n", bo->size / 1024);
        //         vc4_bo_dump_stats(screen);
        // }

        return bo;
}
